﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;

using BizElements.Core;

namespace BizElements.BusinessLayer
{
    /// <summary>Creates business objects of classes derived from the specified base class based on values stored in the entity's subtype field.</summary>
    public class BusinessObjectFactory<TBusinessObjectBase, TTable> : IBusinessObjectFactory<TBusinessObjectBase>
        where TBusinessObjectBase : class, IBusinessObject
        where TTable : IDbTable, new()
    {
        IDictionary<string, IBusinessObjectBuilder<TBusinessObjectBase>> builderRegistry = new Dictionary<string, IBusinessObjectBuilder<TBusinessObjectBase>>();

        #region Constructors.

        /// <summary>Initializes a new instance of <b>BusinessObjectFactory</b> class.</summary>
        public BusinessObjectFactory()
        { 
        }

        /// <summary>Initializes a new instance of <b>BusinessObjectFactory</b> class.</summary>
        /// <param name="subtypeIdPropertyName">Optional name of optional property that determines business object subtype, i.e. derived class. <b>null</b> reference indicates that subtyping is not implemented.</param>
        public BusinessObjectFactory(string subtypeIdPropertyName)
        {
            this.SubtypeIdPropertyName = subtypeIdPropertyName;
        }

        #endregion

        #region Properties.

        /// <summary>Gets the name of optional property that determines business object subtype, i.e. derived class. <b>null</b> or empty string indicates that subtyping is not implemented.</summary>
        public string SubtypeIdPropertyName { get; set; }

        /// <summary>Gets optional base type ID. Default is <b>null</b>.</summary>
        public object BaseTypeId { get; set; }

        #endregion

        /// <summary>Registers builder for the specified object type.</summary>
        /// <param name="typeId">ID of object (sub)type created by the provided builder.</param>
        /// <param name="builder">Business object builder.</param>
        public void RegisterBuilder(object typeId, IBusinessObjectBuilder<TBusinessObjectBase> builder)
        {
            string typeKey = Convert.ToString(typeId);
            this.builderRegistry[typeKey] = builder;
        }

        private IBusinessObjectBuilder<TBusinessObjectBase> GetBuilder(object typeId)
        {
            IBusinessObjectBuilder<TBusinessObjectBase> builder;
            string typeKey = Convert.ToString(typeId);
            this.builderRegistry.TryGetValue(typeKey, out builder);
            if (builder == null)
                throw new ArgumentException("Builder is not registered for the specified object type ID: " + typeId, "typeId");

            return builder;
        }

        /// <summary>Creates a new business object.</summary>
        /// <param name="messageSourceCulture">Optional culture used by the object's multilingual message source.</param>
        /// <param name="typeId">ID of object (sub)type to create. <b>null</b> indicates that no subtyping is used and that factory should create an instance of (non-abstract) base class.</param>
        /// <returns>An instance of base or a derived class associated with the specified type ID.</returns>
        public TBusinessObjectBase NewBusinessObject(CultureInfo messageSourceCulture, object typeId)
        {
            var builder = GetBuilder(typeId);
            var bizObject = builder.NewBusinessObject(messageSourceCulture);
            return bizObject;
        }

        /// <summary>Converts entity to new business object.</summary>
        /// <param name="messageSourceCulture">Optional culture used by the object's multilingual message source.</param>
        /// <param name="dbEntity">Entity whose properties, including entity state, are copied to created business object. Entity must be fetched from data-source, its state must be 'Synchronized' and it must be in a valid state.</param>
        /// <returns>An instance of base or a derived class.</returns>
        public TBusinessObjectBase ConvertToBusinessObject(CultureInfo messageSourceCulture, IEntity dbEntity)
        {
            if (dbEntity.EntityState != EntityState.Synchronized)
                throw new ArgumentException("Entity must be fetched from data-source, its state must be 'Synchronized' and it must be in a valid state.", "dbEntity");

            object typeId = GetTypeId(dbEntity);
            var builder = GetBuilder(typeId);
            var bizObject = builder.ConvertToBusinessObject(messageSourceCulture, dbEntity);
            return bizObject;
        }

        private object GetTypeId(IEntity dbEntity)
        {
            if (this.SubtypeIdPropertyName == null)
                return this.BaseTypeId;

            IDbColumn typeIdField = dbEntity.Table.Columns.GetByPropertyName(this.SubtypeIdPropertyName);
            return dbEntity.GetField(typeIdField);
        }

        /// <summary>Gets database table meta-data that contains base class records. This talbe contains the subtype ID field.</summary>
        /// <returns>Table metadata.</returns>
        public IDbTable GetBaseClassTable()
        {
            return new TTable();
        }
    }    
}